package haffman;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Compress {

    private LinkedList<Node> forest = new LinkedList<>();

    private Map<String, Byte> bufferCode = new ConcurrentHashMap<>(300);

    public Compress() {
        String formatNum ;
        for (int i = 0; i < 256; i++) {
            formatNum = addZeroForNum(Integer.toBinaryString(i),8);
            byte change = bit2byte(formatNum);
            if (change < 0) {
                change += 256;
                bufferCode.put(formatNum, change);
            } else {
                bufferCode.put(formatNum, change);
            }
        }
        System.out.println("-------------------");
    }

    public void initTable(String[] huffmanCode) {
        for (int i = 0; i < huffmanCode.length; i++) {
            huffmanCode[i] = "";
        }
    }

    public String addZeroForNum (String string ,int zeroNum) {
        int strLength = string.length();
        int addTimes = zeroNum - strLength;
        String haveZero = null;
        switch (addTimes) {
            case 7 :
                haveZero = "0000000" + string;
                break;
            case 6 :
                haveZero = "000000" + string;
                break;
            case 5 :
                haveZero = "00000" + string;
                break;
            case 4 :
                haveZero = "0000" + string;
                break;
            case 3 :
                haveZero = "000" + string;
                break;
            case 2 :
                haveZero = "00" + string;
                break;
            case 1 :
                haveZero = "0" + string;
                break;
            case 0 :
                haveZero = string;
                break;
        }
        return haveZero;
    }

    private String addZeroForString(int last, String code) {
        String zeroString = null;
        switch (last) {
            case 7 :
                zeroString = code + "0000000";
                break;
            case 6 :
                zeroString = code + "000000";
                break;
            case 5 :
                zeroString = code + "00000";
                break;
            case 4 :
                zeroString = code + "0000";
                break;
            case 3 :
                zeroString = code + "000";
                break;
            case 2 :
                zeroString = code + "00";
                break;
            case 1 :
                zeroString = code + "0";
                break;
        }
        return zeroString;
    }

    public byte[] countTimes(String path) throws IOException {
        byte[] times = new byte[256];
        FileInputStream fileInputStream = new FileInputStream(path);
        // read file
        byte[] byteBuffer = new byte[1024];
        int value = fileInputStream.read(byteBuffer);
        while (value != -1) {
            for (int i = 0; i < value; i++) {
                times[byteBuffer[i]]++;
            }
            value = fileInputStream.read(byteBuffer);
        }
        fileInputStream.close();
        return times;
    }

    public int getIndex(Node node) {
        for (int i = 0; i < forest.size(); i++) {
            if (node.getData() <= forest.get(i).getData()) {
                return i;
            }
        }
        return forest.size();
    }

    public Node createHuffmanTree(byte[] times) {
        int timeSize = times.length;
        //init HuffmanTree-forest
        for (int i = 0; i < timeSize; i++) {
            if (times[i] != 0) {
                Node node = new Node(times[i], i);
                forest.add(getIndex(node), node);
            }
        }

        Node parent;
        Node left;
        Node right;
        while (forest.size() > 1) {
            left = forest.removeFirst();
            right = forest.removeFirst();
            parent = new Node(left.getData() + right.getData(), -1);
            parent.setLeft(left);
            parent.setRight(right);
            forest.add(getIndex(parent), parent);
        }
        return forest.getFirst();
    }

    public void getHuffmanCode(Node node, String code, String[] huffmanCode) {
        if (node.getLeft() != null) {
            getHuffmanCode(node.getLeft(), code + "0", huffmanCode);
        }
        if (node.getRight() != null) {
            getHuffmanCode(node.getRight(), code + "1", huffmanCode);
        }
        if (node.getLeft() == null && node.getRight() == null) {
            System.out.println(node.getIndex() + " : " + code);
            huffmanCode[node.getIndex()] = code;
        }
    }

    public int changeStringToInt(String s) {
        int v1 = (s.charAt(0)-48) << 7;
        int v2 = (s.charAt(1)-48) << 6;
        int v3 = (s.charAt(2)-48) << 5;
        int v4 = (s.charAt(3)-48) << 4;
        int v5 = (s.charAt(4)-48) << 3;
        int v6 = (s.charAt(5)-48) << 2;
        int v7 = (s.charAt(6)-48) << 1;
        int v8 = (s.charAt(7)-48);
        return v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8;
    }

    private static byte bit2byte(String bString) {
        byte result = 0;
        for (int i = bString.length() - 1, j = 0; i >= 0; i--, j++) {
            result += (Byte.parseByte(bString.charAt(i) + "") << j);
        }

        return result;
    }

    public void writeFirstTable(FileOutputStream fileOutputStream, String[] huffmanCode) throws IOException {
        StringBuffer huffmanCodes = new StringBuffer();
        for (int i = 0; i < 256; i++) {
            fileOutputStream.write(huffmanCode[i].length());
            huffmanCodes.append(huffmanCode[i]);
            fileOutputStream.flush();
        }

        while (huffmanCodes.length() >= 8) {
            fileOutputStream.write(bufferCode.get((huffmanCodes.substring(0, 8))));
            fileOutputStream.flush();
            huffmanCodes.delete(0, 8);
        }

        int lastCodeLength = 8 - huffmanCodes.length();
        for (int i = 0; i < lastCodeLength; i++) {
            huffmanCodes.append("0");
        }

        fileOutputStream.write(bufferCode.get(huffmanCodes.substring(0, 8)));

        fileOutputStream.flush();
    }

    public String readAndMake(FileInputStream fileInputStream, String[] huffmanCode) {
        byte[] inBuf = new byte[1024];
        StringBuffer code = new StringBuffer();
        int codeLength = 8192;
        int value ;
        try {
            /*
            int value = fileInputStream.read(inBuf);
            if (value == -1) {
                return null;
            }
            while (value != -1 || code.length() == codeLength) {
                //将哈夫曼编码拼接成字符串

                for (int i = 0; i < value; i++) {
                    byte position = inBuf[i];
                    code.append(huffmanCode[position]);
                }
                value = fileInputStream.read(inBuf);
            }
            */
            int real = code.length();
            while (true) {

                if (real >= codeLength && (real & 1023) == 0) {
                    if (code.length() == 0) {
                        return null;
                    }else {
                        return code.toString();
                    }
                }
                value = fileInputStream.read(inBuf);
                if (value == -1) {
                    if (code.length() == 0) {
                        return null;
                    }else {
                        return code.toString();
                    }
                }

                for (int i = 0; i < value; i++) {
                    byte position = inBuf[i];
                    code.append(huffmanCode[position]);
                }
                real = code.length();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return code.toString();
    }

    public void handleContent(String code, FileOutputStream fileOutputStream) {
        if (code == null) {
            return;
        }
        byte[] writeBuffer = new byte[1024];
        int codeLength = code.length() / 8;
        int index = 0;
        try {
            for (int i = 0; i < codeLength; i++) {
                byte byteNum = bufferCode.get(code.substring(i << 3, (i + 1) << 3));
                if (index < 1024) {
                    writeBuffer[index] = byteNum;
                    index++;
                }else {
                    fileOutputStream.write(writeBuffer);
                    index = 0;
                }
            }
            if (index != 0) {
                fileOutputStream.write(writeBuffer,0,index);
            }
            fileOutputStream.flush();

            int leftLength = code.length() & 7;
            if (leftLength == 0) {
                fileOutputStream.write(0);
                return;
            }
            int lastIndex = 8 - leftLength;

            code = addZeroForString(lastIndex, code);

            fileOutputStream.write(bufferCode.get(code.substring(code.length() - 8, code.length())));
            System.out.println(code.length());
            fileOutputStream.write(lastIndex);
            fileOutputStream.flush();
        }catch (IOException e) {
            e.printStackTrace();
        }

    }

    public void handleContent(Task task) {
        String code = task.getCode();
        LinkedList<byte[]> linkedList = new LinkedList<>();
        if (code == null) {
            return;
        }
        task.setLinkedList(linkedList);
        byte[] writeBuffer = new byte[1024];
        int codeLength = code.length() / 8;
        System.out.println("compress " + codeLength);
        int index = 0;
        String s ;
        int i = 0;
        for (; i < codeLength; i++) {
            s = code.substring(i << 3, (i + 1) << 3);
            byte byteNum = bufferCode.get(s);
            if (index < 1024) {
                writeBuffer[index] = byteNum;
                index++;
            }else {
                linkedList.add(writeBuffer);
                System.out.println("*********");
                System.out.println(index);
                System.out.println("*********");
                index = 0;
                writeBuffer = new byte[1024];
            }
        }
        if (index != 0) {
            linkedList.add(writeBuffer);
            task.setShift(index - 1);
        }
        int leftLength = code.length() & 7;
        if (leftLength == 0) {
            writeBuffer[index++] = 0;
            task.setShift(index - 1);
            return;
        }

        int lastIndex = 8 - leftLength;

        code = addZeroForString(lastIndex, code);

        writeBuffer[index++] = bufferCode.get(code.substring(code.length() - 8, code.length()));

        writeBuffer[index] = (byte)lastIndex;
        task.setShift(index);
    }

    public void handleContent2(Task task) {

    }

    public void compress(String path, String destPath, String[] huffmanCode) throws IOException {
        FileInputStream fileInputStream = new FileInputStream(path);
        FileOutputStream fileOutputStream = new FileOutputStream(destPath);

        writeFirstTable(fileOutputStream, huffmanCode);

        String code ;
        do {
            code = readAndMake(fileInputStream, huffmanCode);
            handleContent(code, fileOutputStream);
        }while (code != null);


        fileInputStream.close();
        fileOutputStream.close();
    }

    public void getFileValue(String path) throws IOException {
        FileInputStream fileInputStream = new FileInputStream(path);
        int index = fileInputStream.read();
        while (index != -1) {
            System.out.print(index);
            index = fileInputStream.read();
        }
    }

    public static void main(String[] args) throws IOException {

    }
}
